package org.anderground.app.activity.map;

import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.google.android.maps.MapView.LayoutParams.BOTTOM_CENTER;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Observable;
import java.util.Observer;

import org.anderground.app.AndergroundApp;
import org.anderground.app.R;
import org.anderground.app.activity.StationInfoState;
import org.anderground.app.drawer.DrawerProxy;
import org.anderground.app.model.AppModel;
import org.anderground.core.Logger;
import org.anderground.core.drawer.Calculator;
import org.anderground.core.graphics.Point;
import org.anderground.core.graphics.PointF;
import org.anderground.core.graphics.Rect;
import org.anderground.core.model.Line;
import org.anderground.core.model.Station;
import org.anderground.core.util.Coord;
import org.anderground.core.util.Coordinated;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.ZoomControls;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.Projection;

/**
 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
 */
public class MetroMapView extends MapView implements Drawable.Callback,
		Observer {

	private AndergroundApp app;

	DrawerProxy drawer = DrawerProxy.getInstance();

	/** point-to-pixel converter */
	final LocationCalculator calculator = new LocationCalculator();

	/**
	 * @param context
	 * @param app
	 * @param apiKey
	 */
	public MetroMapView(Context context, String apiKey, AndergroundApp app) {
		super(context, apiKey);
		this.app = app;

		AppModel.getInstance().addObserver(this);
		drawer.setCallback(this);

		Overlay metroOverlay = new MetroOverlay();
		getOverlays().add(metroOverlay);

		setSatellite(true);

		setFocusable(true);
		setClickable(true);
		LinearLayout zoomLayout = new LinearLayout(context);
		zoomLayout.setLayoutParams(new ViewGroup.LayoutParams(FILL_PARENT,
				FILL_PARENT));
		zoomLayout.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
		ZoomControls zoomControls = (ZoomControls) getZoomControls();
		zoomControls.setLayoutParams(new ViewGroup.LayoutParams(WRAP_CONTENT,
				WRAP_CONTENT));
		zoomLayout.addView(zoomControls);
		addView(zoomLayout);
	}

	private void resetToFit() {
		Rect bounds = new Rect();
		drawer.copyCoordBounds(bounds);
		GeoPoint center = new GeoPoint(
				(bounds.getLeft() + bounds.getRight()) / 2,
				(bounds.getTop() + bounds.getBottom()) / 2);
		getController().animateTo(center);
		getController().zoomToSpan(bounds.width(), bounds.height());
		postInvalidate();
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		switch (keyCode) {
		case KeyEvent.KEYCODE_DPAD_CENTER:
			resetToFit();
			break;
		default:
			return super.onKeyDown(keyCode, event);

		}
		return false;
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		if (changed) {
			drawer.setBounds(left, top, right, bottom);
		}
		super.onLayout(changed, left, top, right, bottom);
	}

	@Override
	public void invalidateDrawable(Drawable drawable) {
		if (drawable == drawer) {
			postInvalidate();
		}
		super.invalidateDrawable(drawable);
	}

	@Override
	public void update(Observable observable, Object data) {
		if (observable == AppModel.getInstance()) {
			resetToFit();
		}
	}

	private void processStations(final Station[] stations) {
		if (stations.length > 1) {
			Arrays.sort(stations, new Comparator<Station>() {
				public int compare(Station object1, Station object2) {
					return object1.getName().compareToIgnoreCase(
							object2.getName());
				}
			});
			ArrayAdapter<Station> adapter = new ArrayAdapter<Station>(
					getContext(), R.layout.station_row, R.id.station_name,
					stations) {
				private LayoutInflater inflater = LayoutInflater
						.from(getContext());

				public View getView(int position, View convertView,
						ViewGroup parent) {
					if (convertView == null) {
						convertView = inflater.inflate(R.layout.station_item,
								null);

						Station station = getItem(position);

						((TextView) convertView.findViewById(R.id.name))
								.setText(station.getName());
						((TextView) convertView.findViewById(R.id.lines))
								.setText(getStationLinesString(station));
						((ImageView) convertView.findViewById(R.id.icon))
								.setImageDrawable(new StationInfoState.StationDrawable(
										station));
					}

					return convertView;
				}

			};
			new AlertDialog.Builder(getContext())
			// .setIcon(R.drawable.alert_dialog_icon)
					.setTitle("Choose the station").setSingleChoiceItems(
							adapter, -1, new DialogInterface.OnClickListener() {
								public void onClick(DialogInterface dialog,
										int position) {
									showStationInfo(stations[position]);
									dialog.dismiss();
								}
							}).setNegativeButton("Cancel",
							new DialogInterface.OnClickListener() {
								public void onClick(DialogInterface dialog,
										int whichButton) {
								}
							}).show();
		} else if (stations.length == 1) {
			showStationInfo(stations[0]);
		}

	}

	private void showStationInfo(Station station) {
		Coord location = station.getLocation();
		GeoPoint point = new GeoPoint(location.getX(), location.getY());
		LayoutParams params = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT,
				point, BOTTOM_CENTER);
		StationInfoState state = new StationInfoState(app, this, params,
				station);
		app.getStateStack().pushState(state);
	}

	/**
	 * @param station
	 * @return comma separated list of the station's lines
	 */
	private static String getStationLinesString(Station station) {
		final String DIVIDER = ", ";
		Line[] lines = station.getLines();
		StringBuilder linesStr = new StringBuilder(
				lines.length == 0 ? "no lines" : lines[0].getName());
		for (int i = 1; i < lines.length; i++) {
			linesStr.append(DIVIDER);
			linesStr.append(lines[i].getName());
		}
		return linesStr.toString();
	}

	/**
	 * @return the app
	 */
	public AndergroundApp getApp() {
		return app;
	}

	/**
	 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
	 */
	private static class LocationCalculator implements Calculator {

		Projection pixelCalculator;

		private static final android.graphics.Point tempPoint = new android.graphics.Point();

		/**
		 * private constructor
		 */
		private LocationCalculator() {
		}

		public Mode getMode() {
			return Mode.LOCATION;
		}

		public void convert(Coord Coord, PointF pointF) {
			pixelCalculator.toPixels(new GeoPoint(Coord.getX(), Coord.getY()),
					tempPoint);
			pointF.x = tempPoint.x;
			pointF.y = tempPoint.y;
		}

		public void convert(Coord Coord, Point point) {
			pixelCalculator.toPixels(new GeoPoint(Coord.getX(), Coord.getY()),
					tempPoint);
			point.x = tempPoint.x;
			point.y = tempPoint.y;
		}

		public void convert(Coordinated coordinated, PointF pointF) {
			convert(coordinated.getLocation(), pointF);
		}

		public void convert(Coordinated coordinated, Point point) {
			convert(coordinated.getLocation(), point);
		}

		public void convert(PointF coordF, PointF pointF) {
			pixelCalculator.toPixels(new GeoPoint((int) coordF.x,
					(int) coordF.y), tempPoint);
			pointF.x = tempPoint.x;
			pointF.y = tempPoint.y;
		}

		public void convert(PointF coordF, Point point) {
			pixelCalculator.toPixels(new GeoPoint((int) coordF.x,
					(int) coordF.y), tempPoint);
			point.x = tempPoint.x;
			point.y = tempPoint.y;
		}

		public void revert(PointF pointF, PointF coordF) {
			GeoPoint geoPoint = pixelCalculator.fromPixels((int) pointF.x,
					(int) pointF.y);
			coordF.x = geoPoint.getLatitudeE6();
			coordF.y = geoPoint.getLongitudeE6();
		}

		public void revert(Point point, PointF coordF) {
			GeoPoint geoPoint = pixelCalculator.fromPixels(point.x, point.y);
			coordF.x = geoPoint.getLatitudeE6();
			coordF.y = geoPoint.getLongitudeE6();
		}

		public Coord getCoord(Coordinated coordinated) {
			return coordinated.getLocation();
		}

	}

	/**
	 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
	 */
	private class MetroOverlay extends Overlay {

		/**
		 * default constructor.
		 */
		public MetroOverlay() {
		}

		@Override
		public void draw(Canvas canvas, MapView mapView, boolean shadow) {
			if (shadow) {
				return;
			}
			calculator.pixelCalculator = mapView.getProjection();

			drawer.applyCalculator(calculator);
			drawer.draw(canvas);
		}

		@Override
		public boolean onTap(GeoPoint p, MapView mapView) {
			Logger.d("metro overlay onTap event");
			Projection projection = mapView.getProjection();
			calculator.pixelCalculator = projection;
			android.graphics.Point pixels = projection.toPixels(p, null);
			drawer.applyCalculator(calculator);
			processStations(drawer
					.searchStations(new Point(pixels.x, pixels.y)));
			return false;
		}

	}

}